package com.treasure.mall.biz.service.core.user.impl;

import com.treasure.mall.biz.base.AbstractSearchableCrudService;
import com.treasure.mall.biz.base.AuthHelper;
import com.treasure.mall.biz.base.SpecificationAndSort;
import com.treasure.mall.biz.base.UserDetails;
import com.treasure.mall.biz.dto.BaseSaveDTO;
import com.treasure.mall.biz.dto.BaseSearchDTO;
import com.treasure.mall.biz.dto.condition.MerchantSearchCondition;
import com.treasure.mall.biz.dto.user.ModifyBalanceDTO;
import com.treasure.mall.biz.dto.user.ModifyDepositDTO;
import com.treasure.mall.biz.dto.user.ModifyShopDTO;
import com.treasure.mall.biz.entity.embeddable.MerchantData;
import com.treasure.mall.biz.entity.embeddable.MerchantData_;
import com.treasure.mall.biz.entity.embeddable.ReceivingInfo;
import com.treasure.mall.biz.entity.product.CategoryPO;
import com.treasure.mall.biz.entity.product.CategoryPO_;
import com.treasure.mall.biz.entity.user.*;
import com.treasure.mall.biz.mapstruct.CategoryMapStrcut;
import com.treasure.mall.biz.repository.user.DepositLogRepository;
import com.treasure.mall.biz.repository.user.MerchantRepository;
import com.treasure.mall.biz.repository.user.UserRepository;
import com.treasure.mall.biz.service.core.log.AdvanceLogService;
import com.treasure.mall.biz.service.core.product.CategoryService;
import com.treasure.mall.biz.service.core.user.MerchantService;
import com.treasure.mall.biz.service.core.user.MerchantShopFollowService;
import com.treasure.mall.biz.service.core.user.UserService;
import com.treasure.mall.biz.vo.user.ApplyMerchantVO;
import com.treasure.mall.biz.vo.user.MerchantVO;
import com.treasure.mall.zbq.common.MathUtils;
import com.treasure.mall.zbq.common.StringUtilsExt;
import com.treasure.mall.zbq.common.base.ApiResult;
import com.treasure.mall.zbq.common.base.ResultCodeEnum;
import com.treasure.mall.zbq.common.constant.Constant;
import com.treasure.mall.zbq.common.exception.ApiException;
import com.treasure.mall.zbq.common.ienum.base.CodeDescEnumHelper;
import com.treasure.mall.zbq.common.ienum.live.LiveEnums;
import com.treasure.mall.zbq.common.ienum.log.AdvanceLogType;
import com.treasure.mall.zbq.common.ienum.log.LogDataType;
import com.treasure.mall.zbq.common.oss.ResourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * @author allan
 * @version 1.0.0
 * @date 2020/6/11
 */
@Service
public class MerchantServiceImpl extends AbstractSearchableCrudService<MerchantPO, Long> implements MerchantService {

    private MerchantRepository merchantRepository;
    @Resource
    private AdvanceLogService advanceLogService;
    @Resource
    private UserRepository userRepository;
    @Resource
    private MerchantShopFollowService merchantShopFollowService;

    @Resource
    private UserService userService;
    @Resource
    private CategoryService categoryService;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private ResourceService resourceService;
    @Autowired
    private DepositLogRepository depositLogRepository;


    protected MerchantServiceImpl(MerchantRepository repository) {
        super(repository);
        merchantRepository = repository;
    }

    @Override
    public MerchantPO findById(Long aLong) {
        MerchantPO po = super.findById(aLong);
        if (po == null) {
            return null;
        }
        try {
            po.getData().setAvatar(resourceService.getUrl(po.getData().getAvatar()));
            po.getData().setBackgroundImage(resourceService.getUrl(po.getData().getBackgroundImage()));
            po.getInformation().setEnterpriseLegalPersonIdEmblem(resourceService.getUrl(po.getInformation().getEnterpriseLegalPersonIdEmblem()));
            po.getInformation().setEnterpriseLegalPersonIdFace(resourceService.getUrl(po.getInformation().getEnterpriseLegalPersonIdFace()));
            po.getInformation().setCertificateOfIntangibleCultural(resourceService.getUrl(po.getInformation().getCertificateOfIntangibleCultural()));
            po.getInformation().setEnterpriseLicenseCopy(resourceService.getUrl(po.getInformation().getEnterpriseLicenseCopy()));
            po.getInformation().setPersonIdEmblem(resourceService.getUrl(po.getInformation().getPersonIdEmblem()));
            po.getInformation().setPersonIdFace(resourceService.getUrl(po.getInformation().getPersonIdFace()));
        } catch (Exception e) {

        }
        return po;
    }

    @Override
    protected SpecificationAndSort<MerchantPO> specificationAndSort(BaseSearchDTO searchDTO) {
        MerchantSearchCondition condition = (MerchantSearchCondition) searchDTO;
        Specification<MerchantPO> specification = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();


            if (condition.getStatus() != -1) {
                LiveEnums.AuditStatus auditStatus = CodeDescEnumHelper.getEnumTypeByCode(LiveEnums.AuditStatus.class, condition.getStatus());
                predicates.add(cb.equal(root.get(MerchantPO_.auditStatus), auditStatus));
            }

            if (!StringUtils.isEmpty(condition.getKeyword())) {
                predicates.add(cb.or(
                        cb.like(root.get(MerchantPO_.user).get(UserPO_.nickName), likeAll(condition.getKeyword())),
                        cb.like(root.get(MerchantPO_.user).get(UserPO_.mobile), likeAll(condition.getKeyword())),
                        cb.like(root.get(MerchantPO_.data).get(MerchantData_.title), likeAll(condition.getKeyword()))
                ));
            }

            if (!StringUtils.isEmpty(condition.getBeginTime())) {
                LocalDateTime beginTime = StringUtilsExt.localDateTimeFormat(condition.getBeginTime());
                predicates.add(cb.greaterThanOrEqualTo(root.get(MerchantPO_.createTime), beginTime));
            }
            if (!StringUtils.isEmpty(condition.getEndTime())) {
                LocalDateTime endTime = StringUtilsExt.localDateTimeFormat(condition.getEndTime());
                predicates.add(cb.lessThanOrEqualTo(root.get(MerchantPO_.createTime), endTime));
            }

            //根据类目进行筛选
            if (condition.getCatId() != null && condition.getCatId() > 0) {
                predicates.add(cb.equal(root.get(MerchantPO_.data).get(MerchantData_.mainCategory).get(CategoryPO_.catId), condition.getCatId()));
            }

            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
        };

        Sort sort = new Sort(Sort.Direction.DESC, "createTime");

        return new SpecificationAndSort<>(specification, sort);
    }


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return merchantRepository.findByUser_Mobile(username);
    }

    /**
     * 修改用户余额
     *
     * @param modifyBalanceDTO {@link ModifyBalanceDTO}
     */
    @Override
    public synchronized void modifyBalance(ModifyBalanceDTO modifyBalanceDTO) {
        modifyUserBalance(modifyBalanceDTO, LogDataType.MERCHANT_BALANCE);
    }

    /**
     * 修改用户待入账余额
     *
     * @param modifyBalanceDTO {@link ModifyBalanceDTO}
     */
    @Override
    public synchronized void modifyBalanceInFuture(ModifyBalanceDTO modifyBalanceDTO) {
        //修改余额
        MerchantPO userPO = findById(modifyBalanceDTO.getId());
        BigDecimal before, after;
        // 修改前待入账余额
        before = userPO.getBalanceInFuture();
        // 修改后待入账余额
        after = before.add(modifyBalanceDTO.getAmount());
        if (MathUtils.greater(BigDecimal.ZERO, after)) {
            throw new ApiException(ApiResult.errorWith(ResultCodeEnum.ILLEGAL_STATE, "当前待入账余额不足"));
        }
        modifyUserBalance(modifyBalanceDTO, LogDataType.MERCHANT_WAIT_BALANCE);
    }


    /**
     * 修改余额，并记录日志
     *
     * @param modifyBalanceDTO
     */
    private void modifyUserBalance(ModifyBalanceDTO modifyBalanceDTO, LogDataType dataType) {
        //修改余额
        MerchantPO merchantPO = findById(modifyBalanceDTO.getId());
        BigDecimal before, after;
        if (dataType == LogDataType.MERCHANT_BALANCE) {
            // 修改可用余额
            before = merchantPO.getBalance();
            after = before.add(modifyBalanceDTO.getAmount());
            if (MathUtils.greater(BigDecimal.ZERO, after)) {
                throw new ApiException(ApiResult.errorWith(ResultCodeEnum.ILLEGAL_STATE, "当前账户余额不足"));

            }
            merchantRepository.modifyBalance(merchantPO.getId(), modifyBalanceDTO.getAmount());
            merchantRepository.modifyHistoryTotalAmount(merchantPO.getId(), modifyBalanceDTO.getAmount().abs());
        } else {
            before = merchantPO.getBalanceInFuture();
            after = before.add(modifyBalanceDTO.getAmount());
            if (MathUtils.greater(BigDecimal.ZERO, after)) {
                throw new ApiException(ApiResult.errorWith(ResultCodeEnum.ILLEGAL_STATE, "当前待入账余额不足"));
            }
            merchantRepository.modifyBalanceInFuture(merchantPO.getId(), modifyBalanceDTO.getAmount());
        }
        //记录日志
        advanceLogService.save(modifyBalanceDTO, dataType, before, after);
    }

    /**
     * 商户列表
     *
     * @param pageIndex
     * @param pageSize
     * @param condition
     * @return
     */
    @Override
    public Page<MerchantPO> findAll(int pageIndex, int pageSize, MerchantSearchCondition condition) {
        SpecificationAndSort<MerchantPO> specificationAndSort = specificationAndSort(condition);
        PageRequest pageRequest = null;
        if (specificationAndSort.getSort() == null) {
            pageRequest = PageRequest.of(pageIndex - 1, pageSize);
        } else {
            pageRequest = PageRequest.of(pageIndex - 1, pageSize, specificationAndSort.getSort());
        }
        return repository.findAll(specificationAndSort.getSpecification(), pageRequest);
    }

    @Override
    public MerchantPO save(BaseSaveDTO saveDTO) {
        return null;
    }


    /**
     * 提交开店申请数据
     *
     * @param userId
     * @param dto
     */
    @Override
    public MerchantPO applyShop(Long userId, ModifyShopDTO dto) {

        MerchantPO request = merchantRepository.findByUserId(userId);
        if (request == null) {
            request = new MerchantPO();
            request.setAuditStatus(LiveEnums.AuditStatus.WAIT);
        }

        if (request.getAuditStatus().equals(LiveEnums.AuditStatus.SUCCESS)) {
            throw new ApiException(ApiResult.errorWith(ResultCodeEnum.ILLEGAL_STATE, "该商家已审核"));
        }
        UserPO user = userService.findById(userId);
        request.setAuditStatus(LiveEnums.AuditStatus.WAIT);
        request.setData(dto.getData());
        request.getData().setMainCategory(categoryService.findById(dto.getCategoryId()));
        request.setInformation(dto.getInformation());
        request.setUser(user);
        request.setEnableContact(false);
        return repository.save(request);
    }

    /**
     * 修改店铺信息
     *
     * @param userId
     * @param dto
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void modifyShopData(Long userId, ModifyShopDTO dto) {
        MerchantPO request = merchantRepository.findByUserId(userId);
        if (request == null) {
            return;
        }
        MerchantData merchantData = request.getData();
        merchantData.setWechatAccount(dto.getData().getWechatAccount());
        merchantData.setReturnedPurchase(dto.getData().getReturnedPurchase());
        merchantData.setContactWay(dto.getData().getContactWay());
        request.setData(merchantData);
        repository.save(request);
    }

    /**
     * 修改店铺申请，
     *
     * @param id          申请ID
     * @param auditStatus 审核状态
     * @param reason      审核理由
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean modifyAuditStatus(Long id, LiveEnums.AuditStatus auditStatus, String reason) {
        MerchantPO request = findById(id);
        if (request == null) {
            return false;
        }
        if (auditStatus.equals(LiveEnums.AuditStatus.ERROR) && org.apache.commons.lang3.StringUtils.isEmpty(reason)) {
            throw new ApiException(ApiResult.errorWith(ResultCodeEnum.ERROR, "请输入审核理由"));
        }

        int result = merchantRepository.modifyAuditStatus(id, auditStatus, reason);
        if (result <= 0) {
            return false;
        }

        if (auditStatus.equals(LiveEnums.AuditStatus.SUCCESS)) {
            String password = passwordEncoder.encode(Constant.DEFAULT_MERCHANT_PASS);
            merchantRepository.modifyPassword(id, password);
            userRepository.modifyMerchantId(request.getUser().getUserId(), request.getMerchantId());
        }
        return true;
    }

    /**
     * 获取申请数据
     *
     * @param userId
     * @return
     */
    @Override
    public ApplyMerchantVO findApplyShopDataByUserId(Long userId) {
        MerchantPO request = merchantRepository.findByUserId(userId);
        return merchantResponseData(request, userId);
    }

    /**
     * 获取当前店铺信息
     *
     * @param merchantId
     * @param userId
     * @return
     */
    @Override
    public MerchantVO findShopDataByMerchantId(Long merchantId, Long userId) {
        MerchantPO po = this.findById(merchantId);
        return vo(userId, po);
    }

    @Override
    public List<MerchantVO> listVO(List<MerchantPO> pos, Long userId) {
        List<MerchantVO> vos = new ArrayList<>();
        for (MerchantPO po : pos) {
            MerchantVO merchantVO = vo(userId, po);
            if (merchantVO != null) {
                vos.add(merchantVO);
            }
        }
        return vos;
    }

    @Override
    @Transactional
    public void settlePayment(Long merchantId, BigDecimal amount, String subOrderId) {
        //待转正扣除
        ModifyBalanceDTO modifyBalanceDTO = new ModifyBalanceDTO();
        modifyBalanceDTO.setId(merchantId);
        modifyBalanceDTO.setOrderId(subOrderId);
        modifyBalanceDTO.setAmount(amount.negate());
        modifyBalanceDTO.setLogType(AdvanceLogType.LOG_TYPE10);
        this.modifyBalanceInFuture(modifyBalanceDTO);
        //货款增加
        modifyBalanceDTO.setAmount(amount);
        modifyBalanceDTO.setLogType(AdvanceLogType.LOG_TYPE5);
        this.modifyBalance(modifyBalanceDTO);
    }

    @Override
    public void resetPassword(Long merchantId) {
        MerchantPO merchantPO = findById(merchantId);
        merchantPO.setPassword(passwordEncoder.encode(Constant.DEFAULT_MERCHANT_PASS));
        save(merchantPO);
    }

    @Override
    public void changePassword(String origin, String newPass) {
        UserDetails userDetails = AuthHelper.getPrincipal();
        MerchantPO merchantPO = findById(userDetails.getId());
        if (passwordEncoder.matches(origin, merchantPO.getPassword())) {
            merchantPO.setPassword(passwordEncoder.encode(newPass));
            save(merchantPO);

        } else {
            throw new ApiException(ApiResult.errorWith(ResultCodeEnum.ILLEGAL_STATE, "原密码错误"));
        }
    }

    private MerchantVO vo(Long userId, MerchantPO po) {
        ApplyMerchantVO applyMerchantVO = merchantResponseData(po, userId);
        if (applyMerchantVO != null) {
            MerchantVO vo = new MerchantVO();
            vo.setTitle(applyMerchantVO.getData().getTitle());
            vo.setMainCategory(CategoryMapStrcut.INSTANCE.po2vo(applyMerchantVO.getData().getMainCategory()));
            vo.setCertificateOfIntangibleCultural(applyMerchantVO.getInformation().getCertificateOfIntangibleCultural());
            vo.setQualityMargin(applyMerchantVO.getQualityMargin());
            vo.setFollowed(applyMerchantVO.getFollowed());
            vo.setFollows(applyMerchantVO.getFollows());
            vo.setMerchantId(applyMerchantVO.getId());
            vo.setDescription(applyMerchantVO.getData().getDescription());
            vo.setAvatar(applyMerchantVO.getData().getAvatar());
            vo.setBackgroundImage(applyMerchantVO.getData().getBackgroundImage());
            vo.setEnableContact(po.isEnableContact());
            return vo;
        }
        return null;

    }


    private ApplyMerchantVO merchantResponseData(MerchantPO po, Long userId) {
        if (po != null) {
            ApplyMerchantVO vo = new ApplyMerchantVO();
            vo.setData(po.getData());
            vo.setInformation(po.getInformation());
            vo.setReason(po.getReason());
            vo.setAuditStatus(po.getAuditStatus());
            vo.setCreateTime(po.getCreateTime());
            vo.setId(po.getId());
            vo.setQualityMargin(po.getEarnestMoney());

            if (!StringUtilsExt.isURl(vo.getData().getAvatar())) {
                vo.getData().setAvatar(resourceService.getUrl(vo.getData().getAvatar()));
            }
            if (!StringUtilsExt.isURl(vo.getData().getBackgroundImage())) {
                vo.getData().setBackgroundImage(resourceService.getUrl(vo.getData().getBackgroundImage()));
            }
            if (userId != null) {
                vo.setFollowed(merchantShopFollowService.followed(userId, po.getMerchantId()));
            }
            vo.setFollows(merchantShopFollowService.follows(po.getMerchantId()));
            return vo;
        }
        return null;
    }

    /**
     * 修改商户主营类目
     *
     * @param merchantId
     * @param categoryId
     */
    @Override
    public void modifyMainCategory(Long merchantId, Long categoryId) {

        MerchantPO merchantPO = findById(merchantId);

        CategoryPO categoryPO = categoryService.findById(categoryId);

        if (merchantPO == null || categoryPO == null) {
            return;
        }
        merchantPO.getData().setMainCategory(categoryPO);
        repository.save(merchantPO);
    }

    /**
     * 检查指定类目是否关联商品
     *
     * @param categoryId
     * @return
     */
    @Override
    public boolean checkExistByCategoryId(Long categoryId) {
        return merchantRepository.countByCategory(categoryId) > 0;
    }

    @Override
    @Transactional(noRollbackFor = ApiException.class)
    public void modifyDeposit(ModifyDepositDTO dto) {
        //添加日志
        DepositLogPO logPO = new DepositLogPO();
        logPO.setMerchantId(dto.getMerchantId());
        logPO.setLogType(dto.getLogType());
        logPO.setAmount(dto.getAmount());
        logPO.setOrderId(dto.getOrderId());
        logPO.setBillId(dto.getBillId());
        logPO.setRemark(dto.getRemark());

        while (true) {
            MerchantPO po = findById(dto.getMerchantId());
            if (MathUtils.less(dto.getAmount(), BigDecimal.ZERO) && MathUtils.less(po.getEarnestMoney(), dto.getAmount().abs())) {
                //余额不足
                logPO.setSuccess(false);
                logPO.setRemark("保证金余额不足，扣减失败");
                depositLogRepository.save(logPO);
                throw new ApiException(ApiResult.error("保证金余额不足，扣减失败"));
            }
            int index = merchantRepository.modifyDeposit(dto.getMerchantId(), dto.getAmount(), po.getVersion());
            if (index > 0) {
                logPO.setSuccess(true);
                depositLogRepository.save(logPO);
                break;
            }
        }
    }

    @Override
    public Page<DepositLogPO> depositLogs(Long merchantId, int pageIndex, int pageSize) {
        return depositLogRepository.findByMerchantId(merchantId, PageRequest.of(pageIndex - 1, pageSize));
    }

    /**
     * 启用或关闭商户客服功能
     *
     * @param id
     */
    @Override
    public void toggleContactStatus(Long id) {
        MerchantPO po = findById(id);
        merchantRepository.toggleContactStatus(po.getMerchantId(), !po.isEnableContact());
    }

    @Override
    public void updateReturnedPurchaseAddress(Long merchantId, ReceivingInfo receivingInfo) {
        MerchantPO po = findById(merchantId);
        if (po != null) {
            po.getData().setReturnedPurchase(receivingInfo);
            save(po);
        }
    }

    @Override
    @Transactional
    public void setRewardRate(Long merchantId, BigDecimal rate, BigDecimal rebateRateOne, BigDecimal rebateRateTwo) {
        MerchantPO po = findById(merchantId);
        if (po != null) {
            po.setCommissionRate(rate);
            po.setRebateRateOne(rebateRateOne);
            po.setRebateRateTwo(rebateRateTwo);
            save(po);
        }

    }

    @Override
    public boolean exists(Long merchantId) {
        return merchantRepository.countByMerchantId(merchantId) > 0;
    }
}
